#include "Refine.h"
#include <opencv2/core/core.hpp>
#include <stack>

Refine::Refine(int maxSize) : Filter(1, 1), maxSize(maxSize)
{

}


Refine::~Refine(void)
{
}

void Refine::Process()
{
	outputs[0] = inputs[0]->clone();
	cv::Mat mask = cv::Mat(inputs[0]->rows, inputs[0]->cols, CV_8UC1, cv::Scalar(0));
	//for all points of value 0
	for (int i = 0; i < inputs[0]->rows; ++i)
	{
		ushort *p = outputs[0].ptr<ushort>(i);
		for (int j = 0; j < inputs[0]->cols; ++j)
		{
			if (p[j])
				continue;

			std::list<cv::Point2i> border;
			std::list<cv::Point2i> points;

			start(cv::Point2i(j, i), border, points, mask);

			//if border
			if (border.size() && points.size())
			{
				for (auto pt = points.begin(); pt != points.end(); ++pt)
				{
					double avg = 0;
					double weightSum = 0;
					for (auto it = border.begin(); it != border.end(); ++it)
					{
						double distance = (pt->x - it->x)*(pt->x - it->x) + (pt->y - it->y)*(pt->y - it->y);
						double w = f(distance);
						avg += inputs[0]->at<ushort>(*it) * w;
						weightSum += w;
					}

					avg /= weightSum;
					ushort val = cv::saturate_cast<ushort>(avg);
					outputs[0].at<ushort>(*pt) = val;
				}
			}
		}
	}
}

void Refine::start(const cv::Point2i &startPt, std::list<cv::Point2i> &result, std::list<cv::Point2i> &points, cv::Mat &mask)
{
	int minx = INT_MAX;
	int maxx = INT_MIN;
	int miny = INT_MAX;
	int maxy = INT_MIN;

	std::stack<cv::Point2i> toProcess;
	toProcess.push(startPt);

	while (toProcess.size())
	{
		cv::Point2i p = toProcess.top();
		toProcess.pop();

		mask.at<uchar>(p) = 1;
		if (inputs[0]->at<ushort>(p) != 0)
		{
			result.push_back(p);
		}
		else
		{
			points.push_back(p);

			minx = MIN(minx, p.x);
			maxx = MAX(maxx, p.x);
			miny = MIN(miny, p.y);
			maxy = MAX(maxy, p.y);

			if ((maxx - minx) * (maxy - miny) > maxSize)
			{
				points.clear();
				result.clear();
				return;
			}

			cv::Point2i nextPt = cv::Point2i(p.x - 1, p.y);
			if (nextPt.x >= 0 && mask.at<uchar>(nextPt) == 0)
				toProcess.push(nextPt);

			nextPt = cv::Point2i(p.x + 1, p.y);
			if (nextPt.x < inputs[0]->cols && mask.at<uchar>(nextPt) == 0)
				toProcess.push(nextPt);

			nextPt = cv::Point2i(p.x, p.y - 1);
			if (nextPt.y >= 0 && mask.at<uchar>(nextPt) == 0)
				toProcess.push(nextPt);

			nextPt = cv::Point2i(p.x, p.y + 1);
			if (nextPt.y < inputs[0]->rows && mask.at<uchar>(nextPt) == 0)
				toProcess.push(nextPt);
		}
	}
}

double Refine::f(double x)
{
	return x*x;
}